系列:從現場踩坑到 AI 工具 — IT Diagnostic Agent 開發實錄
昨天那個琥珀色,是一組決策原則在 UI 上的落地。
但這組原則的來源不是我某天靈感一來寫下的設計哲學。它是被逼出來的。
因為 GSD 是 Interactive 模式(Day 21),它會不斷停下來問我:
而 GSD 通常會標示一個 Recommended 選項。
一開始我照著 Recommended 選。選了幾次之後我發現不對——它推薦的是「一般情況下的最佳實踐」,但我的專案有一些它不知道的特殊約束。
於是我被迫把「我到底在乎什麼」想清楚。
那組原則就是這樣長出來的:不是設計出來的,是在一連串二選一裡被迫顯形的。
我把當時逐步確立的原則整理出來:
1. 不能破壞既有功能 — 只要有迴歸風險,就選保守選項
2. 破壞性操作要確認,唯讀操作不用 — 覆寫 / 取代 / 刪除要有保護;讀取 / 查詢 / 匯出不需要
3. 內容與介面分離 — 使用者只能編輯診斷內容,UI 字串鎖定
4. 純靜態零依賴 — 不引入任何需要建置流程的東西
5. 手機版垂直堆疊 — 任何新版面都要在窄螢幕上能用
6. 雙語一致 — 任何新文字都要中英文同步
7. 持續性回饋 — 用常駐的 div,不用 toast
8. 每個階段核准前要仔細驗證 — 不能只看 GSD 說「完成」
9. 當 Recommended 跟第 1 或第 2 條衝突時,選更保守的那個
第 9 條是元規則——它決定了當 AI 的建議和我的原則打架時誰贏。
我原本以為第 2 條就是一句話:破壞性操作加一個 confirm(),唯讀操作不加。
一條 if-else 的複雜度。
但我後來回去看實際跑在線上的程式碼,發現它不是二分的,而是三個層級。
刪除 GUIDES Book 和刪除節點,都有真正的 confirm():
if (!confirm('確認刪除 GUIDES Book「' + title + '」?' + refText)) return;
if (!confirm('確認刪除節點「' + name + '」及其所有連線?')) return;
注意兩個細節:
這是讓我意外的地方:匯入功能沒有 confirm() 對話框。
它用的是三個不同的機制:
(a)琥珀色警示按鈕 — 昨天那個 #d97706。這是事前的視覺警告,在你按下去之前就一直在那裡。
(b)層層驗證,通過才動資料
// 1. 能不能解析成 JSON
try { data = JSON.parse(e.target.result); }
catch(err) { showImportFeedback(L().importErrJson, 'error'); return; }
// 2. 版本號對不對
if (data.version !== 1 && data.version !== 2) {
showImportFeedback(L().importErrVersion, 'error'); return; }
// 3. 必要欄位在不在
if (!data.guides || !data.nodes || !data.nodes.zh || !data.nodes.en) {
showImportFeedback(L().importErrFields, 'error'); return; }
// 4. v2 的樹狀結構完整嗎
if (data.version === 2 && (!data.tree ||
!Array.isArray(data.tree.nodes) || !Array.isArray(data.tree.edges))) {
showImportFeedback(L().importErrFields, 'error'); return; }
四道檢查,全部通過才會碰到現有資料。 每一道失敗都有各自的錯誤訊息,而且是雙語的。
(c)Reset to Defaults 提供退路 — 就算真的匯入了不想要的東西,有一個按鈕可以回到預設值。
匯出、瀏覽、走決策樹——零阻力。
當時的選擇可能有一部分是直覺,但回頭看它是對的,而且理由很具體。
Confirm 對話框有一個致命弱點:點擊盲視(click-through blindness)。
任何做過 IT 的人都見過這個現象。使用者看到彈窗,手已經按在「確定」上了。彈窗出現的次數越多,它被閱讀的機率越低。
在 IT 支援的世界裡,這是我們天天在對抗的東西——「你有看到那個警告訊息嗎?」「有啊,我按確定了。」
而顏色不會被點掉。
那個琥珀色按鈕一直在那裡。它不打斷你,但它一直在告訴你「這個跟旁邊那個不一樣」。
所以兩者的性質完全不同:
| Confirm 對話框 | 警示色 | |
|---|---|---|
| 時機 | 動作之後、執行之前 | 動作之前,一直都在 |
| 形式 | 中斷 | 環境訊息 |
| 弱點 | 會被習慣性點掉 | 會被視覺習慣淡化 |
| 適合 | 不可逆、單點的操作 | 可逆、需要事前提醒的操作 |
刪除是不可逆的,適合中斷式的 confirm。
匯入有 Reset 退路,適合事前的警示色 + 硬驗證。
原則沒有變,實作的形式跟著風險性質調整了。
我原本寫的是:
破壞性操作要確認,唯讀操作不用
實際實作出來的是:
保護的強度,要跟「不可逆性」成正比,而不是跟「是不是破壞性」成正比。
| 操作 | 破壞性? | 可逆? | 保護方式 |
|---|---|---|---|
| 刪除節點 | 是 | 否 | 阻斷式 confirm + 說明連帶影響 |
| 匯入覆寫 | 是 | 是(Reset) | 警示色 + 四道驗證 + 持續回饋 |
| 匯出 | 否 | — | 無 |
| 走決策樹 | 否 | — | 無 |
「破壞性」是一個二元標籤,「不可逆性」是一個連續量。而保護機制應該對應後者。
這是我在寫這篇文章時才想清楚的事——實作已經跑在線上好幾週了,但我一直以為那只是一條簡單的 if-else。
順便講這一條,因為它跟第 2 條同源。
Toast(浮動提示,幾秒後自動消失)是現在很流行的回饋形式。GSD 大概也推薦過它。
我選了常駐的 div。理由是同一個邏輯:
如果訊息會自己消失,那它就不算真的告知過使用者。
匯入失敗的錯誤訊息,使用者可能需要五秒才看完、需要複製起來貼給我看、需要對照著檢查自己的 JSON。
一個三秒後消失的錯誤訊息,等於沒有錯誤訊息。
這在 IT 支援上是老經驗了:使用者回報問題時最常說的一句話是「它有跳一個東西,但我來不及看」。
我不想讓我自己的工具製造這種對話。
可控 vs 不可控
我控制不了 GSD 推薦什麼。我控制得了自己的元規則(第 9 條)。
有一條明確的元規則,比逐次判斷可靠得多。 因為逐次判斷會累、會鬆懈、會在第二十次的時候順手選了 Recommended。
核心 vs 外部
核心是「不能弄壞使用者的資料」。confirm 對話框、警示色、驗證邏輯,全部是外部手段。
手段可以換,核心不能換。 這就是為什麼匯入功能可以不用 confirm——它換了手段,沒換核心。
靜態 vs 動態
第 2 條原則本身是靜態的。但「什麼保護強度算適當」要根據不可逆性動態判斷。
原則要簡單到記得住,判斷要細緻到用得上。 這兩件事不衝突,但不能混為一談。
寫到這裡我意識到這組原則最大的用處,不是防止 bug。
是它讓我敢把實作交出去。
如果沒有這九條,我每次面對 GSD 的提問都要從零開始想「這樣做會不會有問題」。那樣的協作成本高到不如自己寫。
有了這九條,大部分選項在三秒內就能判斷。而剩下那些三秒判斷不了的,才是真正需要我動腦的地方。
明確的原則,是委派的前提。 不管你委派的對象是 AI 還是人。
我做了二十年 IT,這九條原則裡沒有一條是我在這個專案裡新學到的。
「刪除要確認」、「錯誤訊息不能自己消失」、「不確定就選保守的」——這些是任何做過維運的人都會有的直覺。
但把直覺寫成明文,是完全不同的一件事。
而逼我寫下來的,是一個會不斷問我「A 還是 B」的 agent。
AI 協作的一個副作用是:它會逼你把你自己的原則說清楚。 因為它不能像人類同事那樣,靠著跟你共事三年來猜你在乎什麼。
它只能照你說的做。所以你必須說得出來。
明天預告: 前面三天講的都是寫程式。但我用 Claude 做的事情不只寫程式——包括你正在讀的這三十篇文章,它們的草稿、修正、版本管理,全部都在同一個協作流程裡。
作者:Rich Chang | IT 基礎建設工程師 | 越南・柬埔寨・台灣